using System.Linq;
using System;
using System.IO;
using System.Text.RegularExpressions;
using tomenglertde.SetupProject.Tools;

namespace tomenglertde.SetupProject.Model
{
    public static class Parser
    {
        public static readonly Regex ValuePattern = new Regex("^\"(?<name>.+)\"\\s*=\\s*\"(?<valueType>\\d+):(?<value>.*)\"$");
        public static readonly Regex NodePattern = new Regex("^\"(?<name>.+)\"$");

        public static NodeEntity Parse(string fileName)
        {
            using (var lineEnumerator = new StringEnumerator(File.ReadAllLines(fileName).GetEnumerator()))
            {
                if (!lineEnumerator.MoveNext())
                    throw new InvalidOperationException("File is empty.");

                return GetNode(lineEnumerator, null);
            }
        }

        private static NodeEntity GetNode(StringEnumerator lineEnumerator, NodeEntity parent)
        {
            var nodeMatch = NodePattern.Match(lineEnumerator.Current.Trim());
            if (!nodeMatch.Success)
                throw new InvalidOperationException(GetErrorText(lineEnumerator, "Node name expected"));

            var nodeName = nodeMatch.Groups["name"].Value;

            if (!lineEnumerator.MoveNext())
                throw new InvalidOperationException(GetErrorText(lineEnumerator, "Unexpected end of file"));

            if (lineEnumerator.Current != "{")
                throw new InvalidOperationException(GetErrorText(lineEnumerator, "'{' expected"));

            var node = new NodeEntity(parent) { Name = nodeName };

            while (lineEnumerator.MoveNext())
            {
                var line = lineEnumerator.Current;

                if (line == "}")
                {
                    break;
                }

                if (GetValue(node, line))
                    continue;

                node.Children.Add(GetNode(lineEnumerator, node));
            }

            return node;
        }

        public static bool GetValue(NodeEntity node, string line)
        {
            var valueMatch = ValuePattern.Match(line);

            if (!valueMatch.Success)
                return false;

            var value = new ValueEntity()
            {
                Name = valueMatch.Groups["name"].Value,
                ValueType = Int32.Parse(valueMatch.Groups["valueType"].Value),
                Value = valueMatch.Groups["value"].Value
            };

            node.Values.Add(value);
            return true;
        }

        public static string GetErrorText(StringEnumerator lineEnumerator, string message)
        {
            return "Error in line " + lineEnumerator.LineNumber + ": " + message;
        }

        public static void Save(string fileName, NodeEntity root)
        {
            using (var writer = new StreamWriter(fileName))
            {
                Save(writer, root);
            }
        }

        public static void Save(TextWriter writer, NodeEntity root)
        {
            WriteNode(new IndentWriter(writer), root);
        }

        private static void WriteNode(IndentWriter writer, NodeEntity node)
        {
            writer.WriteLine(node.ToString());
            writer.WriteLine("{");

            foreach (var entity in node.Entities)
            {
                var value = entity as ValueEntity;
                if (value != null)
                {
                    writer.WriteLine(value.ToString());
                    continue;
                }

                var child = entity as NodeEntity;
                if (child != null)
                {
                    writer.Indent();
                    WriteNode(writer, child);
                    writer.Unindent();
                    continue;
                }

                throw new InvalidOperationException("Unexpected entity type");
            }

            /*
            foreach (var value in node.Values)
            {
                writer.WriteLine(value.ToString());
            }

            writer.Indent();

            foreach (var child in node.Children)
            {
                WriteNode(writer, child);
            }

            writer.Unindent();
            */

            writer.WriteLine("}");
        }

        private class IndentWriter
        {
            private readonly TextWriter _writer;
            private int _indent;
            private string _indentString = string.Empty;
            private const int IntentBlockSize = 4;

            public IndentWriter(TextWriter writer)
            {
                _writer = writer;
            }

            public void WriteLine(string text)
            {
                _writer.WriteLine(_indentString + text);
            }

            public void Indent()
            {
                _indent += IntentBlockSize;
                _indentString = new String(' ', _indent);
            }

            public void Unindent()
            {
                _indent -= IntentBlockSize;
                _indentString = new String(' ', _indent);
            }

        }
    }
}